// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Copyright 2022 The Lynx Authors. All rights reserved.
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.

#ifndef CLAY_FLOW_FRAME_TIMINGS_H_
#define CLAY_FLOW_FRAME_TIMINGS_H_

#include <array>
#include <memory>
#include <mutex>
#include <string>
#include <string_view>
#include <vector>

#include "base/include/fml/macros.h"
#include "base/include/fml/time/time_delta.h"
#include "base/include/fml/time/time_point.h"
#include "clay/common/settings.h"

#define TRACE_EVENT_WITH_FRAME_NUMBER(recorder, category_group, name) \
  TRACE_EVENT(category_group, name, "frame_number",                   \
              recorder->GetFrameNumberTraceArg())

namespace clay {

enum class FrameTimingKey : uint8_t {
  kLayoutStart,
  kLayoutEnd,
  kDoAnimationStart,
  kDoAnimationEnd,
  kPaintStart,
  kPaintEnd,
  kBuildFrameStart,
  kBuildFrameEnd,
  kDoDrawStart,
  kAcquireFrameStart,
  kAcquireFrameEnd,
  kRasterStart,
  kRasterEnd,
  kSubmitFrameStart,
  kSubmitFrameEnd,
  kDoDrawEnd,
  kCount,
};

struct FrameTimingItem {
  FrameTimingKey key;
  uint64_t timestamp;
};

inline constexpr std::array<std::string_view,
                            static_cast<size_t>(FrameTimingKey::kCount)>
    kFrameTimingKeyStrings = {
        "layoutStart",       "layoutEnd",        "doAnimationStart",
        "doAnimationEnd",    "clayPaintStart",   "clayPaintEnd",
        "buildFrameStart",   "buildFrameEnd",    "doDrawStart",
        "acquireFrameStart", "acquireFrameEnd",  "rasterStart",
        "rasterEnd",         "submitFrameStart", "submitFrameEnd",
        "doDrawEnd",
};
inline constexpr std::string_view ToStringView(FrameTimingKey key) {
  const auto index = static_cast<size_t>(key);
  if (index < kFrameTimingKeyStrings.size()) {
    return kFrameTimingKeyStrings[index];
  } else {
    return "unknown";
  }
}

class RasterCache;

/// Records timestamps for various phases of a frame rendering process.
///
/// Recorder is created on vsync and destroyed after the rasterization of the
/// frame. This class is thread safe and doesn't require additional
/// synchronization.
class FrameTimingsRecorder {
 public:
  /// Various states that the recorder can be in. When created the recorder is
  /// in an uninitialized state and transitions in sequential order of the
  /// states.
  enum class State : uint32_t {
    kUninitialized,
    kVsync,
    kBuildStart,
    kBuildEnd,
    kRasterStart,
    kRasterEnd,
  };

  /// Default constructor, initializes the recorder with State::kUninitialized.
  FrameTimingsRecorder();

  /// Constructor with a pre-populated frame number.
  explicit FrameTimingsRecorder(uint64_t frame_number);

  ~FrameTimingsRecorder();

  /// Timestamp of the vsync signal.
  fml::TimePoint GetVsyncStartTime() const;

  /// Timestamp of when the frame was targeted to be presented.
  ///
  /// This is typically the next vsync signal timestamp.
  fml::TimePoint GetVsyncTargetTime() const;

  /// Timestamp of when the frame building started.
  fml::TimePoint GetBuildStartTime() const;

  /// Timestamp of when the frame was finished building.
  fml::TimePoint GetBuildEndTime() const;

  /// Timestamp of when the frame rasterization started.
  fml::TimePoint GetRasterStartTime() const;

  /// Timestamp of when the frame rasterization finished.
  fml::TimePoint GetRasterEndTime() const;

  /// Timestamp of when the frame rasterization is complete in wall-time.
  fml::TimePoint GetRasterEndWallTime() const;

  /// Duration of the frame build time.
  fml::TimeDelta GetBuildDuration() const;

  fml::TimePoint GetVsyncTimeOnGeneration() const;

  fml::TimePoint GetLastDrawVsyncTime() const;

  int GetVsyncSequenceId() const;

  bool HasValidVsyncSequenceId() const;

  bool GetForced() const;

  /// Count of the layer cache entries
  size_t GetLayerCacheCount() const;

  /// Total Bytes in all layer cache entries
  size_t GetLayerCacheBytes() const;

  /// Count of the picture cache entries
  size_t GetPictureCacheCount() const;

  /// Total Bytes in all picture cache entries
  size_t GetPictureCacheBytes() const;

  /// Records a vsync event.
  void RecordVsync(fml::TimePoint vsync_start, fml::TimePoint vsync_target);

  /// Records a build start event.
  void RecordBuildStart(fml::TimePoint build_start);

  /// Records a build end event.
  void RecordBuildEnd(fml::TimePoint build_end);

  /// Records a raster start event.
  void RecordRasterStart(fml::TimePoint raster_start);

  void RecordLastDrawVsyncTime(fml::TimePoint last_draw_vsync);

  void RecordVsyncTimeOnGeneration(fml::TimePoint time);

  void RecordForced(bool forced);

  void RecordVsyncSequenceId(int id);

  /// Clones the recorder until (and including) the specified state.
  std::unique_ptr<FrameTimingsRecorder> CloneUntil(State state);

  /// Records a raster end event, and builds a `FrameTiming` that summarizes all
  /// the events. This summary is sent to the framework.
  FrameTiming RecordRasterEnd(const RasterCache* cache = nullptr);

  /// Returns the frame number. Frame number is unique per frame and a frame
  /// built earlier will have a frame number less than a frame that has been
  /// built at a later point of time.
  uint64_t GetFrameNumber() const;

  /// Returns the frame number in a fml tracing friendly format.
  const char* GetFrameNumberTraceArg() const;

  /// Returns the recorded time from when `RecordRasterEnd` is called.
  FrameTiming GetRecordedTime() const;

  void RecordFrameTime(FrameTimingKey key);

  std::vector<FrameTimingItem> TakeFrameTimings();

 private:
  static std::atomic<uint64_t> frame_number_gen_;

  mutable std::mutex state_mutex_;
  State state_ = State::kUninitialized;

  const uint64_t frame_number_;
  const std::string frame_number_trace_arg_val_;

  fml::TimePoint vsync_start_;
  fml::TimePoint vsync_target_;
  fml::TimePoint build_start_;
  fml::TimePoint build_end_;
  fml::TimePoint raster_start_;
  fml::TimePoint raster_end_;
  fml::TimePoint raster_end_wall_time_;
  // time of vsync signal arrives.
  fml::TimePoint frame_start_time_;
  // `frame_start_time` + 1000ms/refreshRate.
  fml::TimePoint frame_target_time_;
  fml::TimePoint last_draw_vsync_time_;
  fml::TimePoint vsync_time_on_generation_;
  // force to produce layer_tree even the pipeLine is full.
  bool forced_ = false;
  int vsync_sequence_id_ = -1;
  size_t layer_cache_count_;
  size_t layer_cache_bytes_;
  size_t picture_cache_count_;
  size_t picture_cache_bytes_;

  // Set when `RecordRasterEnd` is called. Cannot be reset once set.
  FrameTiming timing_;

  std::vector<FrameTimingItem> frame_timings_;

  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(FrameTimingsRecorder);
};

}  // namespace clay

#endif  // CLAY_FLOW_FRAME_TIMINGS_H_
